﻿using System;
using System.Drawing;
using System.Windows.Forms;

namespace _2048 {
    // 游戏
    class Game {
        // 回调
        public interface Callback {
            void OnGameOver(bool isWon);    // 游戏结束事件
        }

        // 状态
        public enum Status {
            END,    // 已结束
            PLAYING // 游戏中
        }

        public readonly Brush mBrush;           // 背景画刷
        private readonly Callback mCallback;    // 回调
        private readonly Graphics mDrawer;      // 绘图者
        public readonly Font mFont;             // 字体
        public readonly Brush mFontBrush;       // 字体画刷
        private readonly Control mGameBoard;    // 游戏区域
        private int[,] mNumbers;                // 数字矩阵
        private readonly Random mRandom;        // 随机数生成器
        public readonly Pen mPen;               // 边框画笔
        public Status mStatus;                  // 当前状态

        /**
         * 构造方法。
         * @param callback  回调
         * @param drawer    绘图者
         * @param gameBoard 游戏区域
         */
        public Game(Callback callback, Graphics drawer, Control gameBoard) {
            // 初始化成员
            mBrush = new SolidBrush(Const.GAME_BOARD_COLOR);
            mCallback = callback;
            mDrawer = drawer;
            mFont = new Font(Const.GAME_BOARD_FONT_NAME, Const.GAME_BOARD_FONT_SIZE);
            mFontBrush = new SolidBrush(Color.Black);
            mGameBoard = gameBoard;
            mPen = new Pen(Color.Black);
            mRandom = new Random();
            mStatus = Status.END;

            // 重绘游戏区域
            this.DrawGameBoard();
        }

        /**
         * 移动之后要做的事。
         * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
         */
        private void AfterMove(bool changed) {
            // 重绘游戏区域
            this.DrawGameBoard();

            // 画所有数字
            this.DrawAllNumbers();

            // 如果胜利或失败，则游戏结束
            if (this.IsWon()) {
                this.GameOver(true);
                return;
            }
            if (this.IsLost()) {
                this.GameOver(false);
                return;
            }

            // 创建下一个数
            if (changed) {
                this.CreateRandomNumber();
            }
        }

        /**
         * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
         */
        private void CreateRandomNumber() {
            int row = mRandom.Next(0, Const.GAME_BOARD_GRID_COUNT_V);
            int line = mRandom.Next(0, Const.GAME_BOARD_GRID_COUNT_H);

            while (mNumbers[row, line] != 0) {
                row = mRandom.Next(0, Const.GAME_BOARD_GRID_COUNT_V);
                line = mRandom.Next(0, Const.GAME_BOARD_GRID_COUNT_H);
            }

            mNumbers[row, line] = mRandom.Next(0, 8) <= 3 ? 2: 4;
            this.DrawOneNumber(line, row, mNumbers[row, line]);
            mGameBoard.Invalidate(false);
        }

        /**
         * 画所有数字。
         */
        private void DrawAllNumbers() {
            for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                    if (mNumbers[i, j] != 0) {
                        this.DrawOneNumber(j, i, mNumbers[i, j]);
                    }
                }
            }

            mGameBoard.Invalidate(false);
        }

        /**
         * 画游戏区域。
         */
        private void DrawGameBoard() {
            // 画边框
            mDrawer.DrawRectangle(mPen, 0, 0, Const.GAME_BOARD_WIDTH - 1, Const.GAME_BOARD_HEIGHT - 1);
            
            // 画背景
            mDrawer.FillRectangle(mBrush, 1, 1, Const.GAME_BOARD_WIDTH - 2, Const.GAME_BOARD_HEIGHT - 2);

            // 画竖线
            int i;
            for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                mDrawer.DrawLine(mPen, Const.GRID_SIZE * i, 0, Const.GRID_SIZE * i, Const.GAME_BOARD_HEIGHT);
            }

            // 画横线
            for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_H; i++) {
                mDrawer.DrawLine(mPen, 0, Const.GRID_SIZE * i, Const.GAME_BOARD_WIDTH, Const.GRID_SIZE * i);
            }

            // 通知游戏区域重绘
            mGameBoard.Invalidate(false);
        }

        /**
         * 画单个数字。
         * @param line      列
         * @param row       行
         * @param number    要画的数字
         */
        private void DrawOneNumber(int line, int row, int number) {
            // 生成属性
            StringFormat stringFormat = new StringFormat();
            stringFormat.Alignment = StringAlignment.Center;
            stringFormat.LineAlignment = StringAlignment.Center;

            // 计算位置
            int x = line * Const.GRID_SIZE;
            int y = row * Const.GRID_SIZE;
            RectangleF rect = new RectangleF(x, y, Const.GRID_SIZE, Const.GRID_SIZE);

            // 画数字
            mDrawer.DrawString(number.ToString(), mFont, mFontBrush, rect, stringFormat);
        }

        /**
         * 游戏结束。
         * @param isWon 胜利则为 true，否则为 false
         */
        private void GameOver(bool isWon) {
            // 将状态设置为已结束
            mStatus = Status.END;

            // 发送通知
            mCallback.OnGameOver(isWon);
        }

        /**
         * 判断是否失败。
         * @return 失败则返回 true，否则返回 false
         */
        private bool IsLost() {
            int i, j;   // 循环变量

            // 横向检查所有数字
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                    if (mNumbers[i, j] == 0 || mNumbers[i, j] == mNumbers[i, j + 1]) {
                        return false;
                    }
                }
            }

            // 纵向检查所有数字
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V - 1; i++) {
                    if (mNumbers[i, j] == mNumbers[i + 1, j]) {
                        return false;
                    }
                }
            }

            // 判断最右边一列是否有零
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                if (mNumbers[i, 3] == 0) {
                    return false;
                }
            }

            return true;
        }

        /**
         * 判断是否胜利。
         * @return 胜利则返回 true，否则返回 false
         */
        private bool IsWon() {
            // 检查所有数字
            for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                    if (mNumbers[i, j] == Const.WIN_NUMBER) {
                        return true;
                    }
                }
            }

            return false;
        }

        /**
         * 下移。
         */
        private void MoveDown() {
            int i, j, k;            // 循环变量
            int ignore = -1;        // 忽略的格
            bool changed = false;   // 是否相加、移动

            // 从左到右遍历每一列
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                // 每列从上到下遍历每一格，判断是否有相同数，有则相加
                for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                    // 如果该数为零或之前被加过，则什么也不做
                    if (mNumbers[i, j] == 0 || i == ignore) {
                        continue;
                    }

                    // 不为零则从该数下方逐个检查
                    for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                        // 遇到零跳过
                        if (mNumbers[k, j] == 0) {
                            continue;
                        }

                        // 如果两数不相等，则结束本次循环
                        if (mNumbers[k, j] != mNumbers[i, j]) {
                            break;
                        }

                        // 两数相等则相加
                        mNumbers[k, j] += mNumbers[i, j];
                        mNumbers[i, j] = 0;
                        ignore = k;
                        changed = true;
                    }
                }

                // 所有数字向下移动
                for (i = Const.GAME_BOARD_GRID_COUNT_V - 2; i >= 0; i--) {
                    // 如果该数为零（没有数），则什么也不做
                    if (mNumbers[i, j] == 0) {
                        continue;
                    }

                    // 不为零则向下移动
                    for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                        if (mNumbers[k, j] == 0) {
                            mNumbers[k, j] = mNumbers[k - 1, j];
                            mNumbers[k - 1, j] = 0;
                            changed = true;
                        }
                    }
                }
            }

            this.AfterMove(changed);
        }

        /**
         * 左移。
         */
        private void MoveLeft() {
            int i, j, k;            // 循环变量
            int ignore = -1;        // 忽略的格
            bool changed = false;   // 是否相加、移动

            // 从上到下遍历每一行
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 每行从右到左遍历每一格，判断是否有相同数，有则相加
                for (j = Const.GAME_BOARD_GRID_COUNT_H - 1; j > 0; j--) {
                    // 如果该数为零或之前被加过，则什么也不做
                    if (mNumbers[i, j] == 0 || j == ignore) {
                        continue;
                    }

                    // 不为零则从该数左面逐个检查
                    for (k = j - 1; k >= 0; k--) {
                        // 遇到零跳过
                        if (mNumbers[i, k] == 0) {
                            continue;
                        }

                        // 如果两数不相等，则结束本次循环
                        if (mNumbers[i, k] != mNumbers[i, j]) {
                            break;
                        }

                        // 两数相等则相加
                        mNumbers[i, k] += mNumbers[i, j];
                        mNumbers[i, j] = 0;
                        ignore = k;
                        changed = true;
                    }
                }

                // 所有数字向左移动
                for (j = 1; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                    // 如果该数为零（没有数），则什么也不做
                    if (mNumbers[i, j] == 0) {
                        continue;
                    }

                    // 不为零则向左移动
                    for (k = j - 1; k >= 0; k--) {
                        if (mNumbers[i, k] == 0) {
                            mNumbers[i, k] = mNumbers[i, k + 1];
                            mNumbers[i, k + 1] = 0;
                            changed = true;
                        }
                    }
                }
            }

            this.AfterMove(changed);
        }

        /**
         * 右移。
         */
        private void MoveRight() {
            int i, j, k;            // 循环变量
            int ignore = -1;        // 忽略的格
            bool changed = false;   // 是否相加、移动

            // 从上到下遍历每一行
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 每行从左到右遍历每一格，判断是否有相同数，有则相加
                for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                    // 如果该数为零或之前被加过，则什么也不做
                    if (mNumbers[i, j] == 0 || j == ignore) {
                        continue;
                    }

                    // 不为零则从该数右面逐个检查
                    for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                        // 遇到零跳过
                        if (mNumbers[i, k] == 0) {
                            continue;
                        }

                        // 如果两数不相等，则结束本次循环
                        if (mNumbers[i, k] != mNumbers[i, j]) {
                            break;
                        }

                        // 两数相等则相加
                        mNumbers[i, k] += mNumbers[i, j];
                        mNumbers[i, j] = 0;
                        ignore = k;
                        changed = true;
                    }
                }

                // 所有数字向右移动
                for (j = Const.GAME_BOARD_GRID_COUNT_H - 2; j >= 0; j--) {
                    // 如果该数为零（没有数），则什么也不做
                    if (mNumbers[i, j] == 0) {
                        continue;
                    }

                    // 不为零则向右移动
                    for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                        if (mNumbers[i, k] == 0) {
                            mNumbers[i, k] = mNumbers[i, k - 1];
                            mNumbers[i, k - 1] = 0;
                            changed = true;
                        }
                    }
                }
            }

            this.AfterMove(changed);
        }

        /**
         * 上移。
         */
        private void MoveUp() {
            int i, j, k;            // 循环变量
            int ignore = -1;        // 忽略的格
            bool changed = false;   // 是否相加、移动

            // 从左到右遍历每一列
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                // 每列从下到上遍历每一格，判断是否有相同数，有则相加
                for (i = Const.GAME_BOARD_GRID_COUNT_V - 1; i > 0; i--) {
                    // 如果该数为零或之前被加过，则什么也不做
                    if (mNumbers[i, j] == 0 || i == ignore) {
                        continue;
                    }

                    // 不为零则从该数上方逐个检查
                    for (k = i - 1; k >= 0; k--) {
                        // 遇到零跳过
                        if (mNumbers[k, j] == 0) {
                            continue;
                        }

                        // 如果两数不相等，则结束本次循环
                        if (mNumbers[k, j] != mNumbers[i, j]) {
                            break;
                        }

                        // 两数相等则相加
                        mNumbers[k, j] += mNumbers[i, j];
                        mNumbers[i, j] = 0;
                        ignore = k;
                        changed = true;
                    }
                }

                // 所有数字向上移动
                for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                    // 如果该数为零（没有数），则什么也不做
                    if (mNumbers[i, j] == 0) {
                        continue;
                    }
                    
                    // 不为零则向上移动
                    for (k = i - 1; k >= 0; k--) {
                        if (mNumbers[k, j] == 0) {
                            mNumbers[k, j] = mNumbers[k + 1, j];
                            mNumbers[k + 1, j] = 0;
                            changed = true;
                        }
                    }
                }
            }

            this.AfterMove(changed);
        }

        /**
         * 按键事件的响应方法。
         * @param key   按键
         * @return 事件在此被处理则返回 true，否则 false
         */
        public bool OnKeyPressed(Keys key) {
            if (mStatus != Status.PLAYING) {
                return false;
            }

            switch (key) {
                case Keys.Down:
                    this.MoveDown();
                    return true;
                case Keys.Left:
                    this.MoveLeft();
                    return true;
                case Keys.Right:
                    this.MoveRight();
                    return true;
                case Keys.Up:
                    this.MoveUp();
                    return true;
            }

            return false;
        }

        /**
         * 游戏开始。
         */
        public void Start() {
            // 重绘游戏区域
            this.DrawGameBoard();

            // 数字矩阵清零
            mNumbers = new int[Const.GAME_BOARD_GRID_COUNT_H, Const.GAME_BOARD_GRID_COUNT_V];

            // 创建随机数字
            this.CreateRandomNumber();

            // 将状态设置为游戏中
            mStatus = Status.PLAYING;
        }
    }
}
